package csm.common;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

/**
 * File I/O 및 Handling 도구를 제공하는 클래스
 *
 * @author seungmun
 * @since 2006.1.18.
 *
 */
public final class FileUtil
{
	private static FileFilter getDirectoryFilter()
	{
		FileFilter fileFilter = new FileFilter()
		{
			public boolean accept(File file)
			{
				return file.isDirectory();
			}
		};
		return fileFilter;
	}

	private static FileFilter getFileFilter()
	{
		FileFilter fileFilter = new FileFilter()
		{
			public boolean accept(File file)
			{
				return file.isFile();
			}
		};
		return fileFilter;
	}

	/**
	 * 지정된 경로의 하위 폴더목록을 가져옵니다.
	 *
	 * @param parentPath
	 * @return
	 */
	public static File[] getDirectoryList(String parentPath)
	{
		File dir = new File(parentPath);
		if (!dir.exists()) return null;
		return getDirectoryList(dir);
	}

	/**
	 * 지정된 경로의 하위 폴더목록을 가져옵니다.
	 *
	 * @param dir
	 * @return
	 */
	public static File[] getDirectoryList(File dir)
	{
		if (dir == null || !dir.exists()) return null;
		return dir.listFiles(getDirectoryFilter());
	}

	/**
	 * 지정된 경로의 파일목록을 가져옵니다.
	 *
	 * @param parentPath
	 * @return
	 */
	public static File[] getFileList(String parentPath)
	{
		File dir = new File(parentPath);
		if (!dir.exists()) return null;
		return getFileList(dir);
	}

	/**
	 * 지정된 경로의 파일목록을 가져옵니다.
	 *
	 * @param dir
	 * @return
	 */
	public static File[] getFileList(File dir)
	{
		if (dir == null || !dir.exists()) return null;
		return dir.listFiles(getFileFilter());
	}

	/**
	 * 디렉토리를 삭제합니다.
	 *
	 * @param path
	 * @param recursive
	 *            이 디렉토리, 파일 그리고 하위 디렉터리까지 모두 삭제하려면 <code>true</code>, 그렇지 않으면 <code>false</code>를 지정합니다.
	 */
	public static void deleteDirectory(String path, boolean recursive)
	{
		deleteDirectory(new File(path), recursive);
	}

	/**
	 * 디렉토리를 삭제합니다.
	 *
	 * @param dir
	 *            삭제할 디렉토리
	 * @param recursive
	 *            이 디렉토리, 파일 그리고 하위 디렉터리까지 모두 삭제하려면 <code>true</code>, 그렇지 않으면 <code>false</code>를 지정합니다.
	 */
	public static void deleteDirectory(File dir, boolean recursive)
	{
		// 전달된 디렉토리를 삭제한다.
		// 디렉토리내에 파일이 존재하거나 하위에 폴더가 존재하는 경우도 고려한다.
		if (!dir.exists()) return;

		File[] files = dir.listFiles();

		if (files.length > 0 && recursive)
		{
			for (File f : files)
			{
				if (f.isDirectory())
					deleteDirectory(f, recursive);
				else
					f.delete();
			}
		}

		dir.delete();
	}

	/**
	 * 파일 혹은 디렉토리의 이름을 변경합니다
	 *
	 * @param src 변경대상이 되는 파일
	 * @param to 새로운 파일이름
	 */
	public static void renameFile(File src, File to)
	{
		src.renameTo(to);
	}

	/**
	 * 파일을 이동시킵니다.
	 *
	 * @param sourceFile
	 *            이동 대상이 되는 파일
	 * @param destFile
	 *            최종적으로 이동시킬 경로와 새로운 파일명.
	 * @return
	 * @throws IOException
	 */
	public static boolean moveFile(File sourceFile, File destFile) throws IOException
	{
		if (copyFile(sourceFile, destFile))
		{
			sourceFile.delete();
			return true;
		}
		return false;
	}

	/**
	 * masterFile뒤에 sourceFile을 추가합니다.
	 *
	 * @param sourceFile
	 *            추가될 파일
	 * @param masterFile
	 *            추가되는 대상 파일
	 * @param isDeleteSourceFile
	 *            sourceFile을 삭제할지 여부(true : 삭제함)
	 * @return
	 * @throws IOException
	 */
	public static boolean appendFile(File sourceFile, File masterFile, boolean isDeleteSourceFile) throws IOException
	{
		FileInputStream oFIS = null;
		FileOutputStream oFOS = null;

		try
		{
			oFIS = new FileInputStream(sourceFile);
			oFOS = new FileOutputStream(masterFile, true);

			byte[] oBuffer = new byte[1024 * 4];
			int nRead = 0;

			while ((nRead = oFIS.read(oBuffer)) != -1)
			{
				oFOS.write(oBuffer, 0, nRead);
			}

			oFIS.close();
			oFOS.close();
			oFIS = null;
			oFOS = null;

			if (isDeleteSourceFile)
				sourceFile.delete();

			return true;
		}
		catch (Exception e)
		{
			// TODO 예외처리 필요
			if (oFIS != null)
				oFIS.close();

			if (oFOS != null)
				oFOS.close();

			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 파일을 복사합니다.<br>
	 * destFile이 이미 존재하는 경우는 덮어쓰게 됩니다.
	 *
	 * @param sourceFile
	 * @param destFile
	 * @return
	 * @throws IOException
	 */
	public static boolean copyFile(File sourceFile, File destFile) throws IOException
	{
		FileInputStream oFIS = null;
		FileOutputStream oFOS = null;
		File targetFile = destFile;

		try
		{
			/*
			 * if (!targetFile.isFile()) { checkDirectory(targetFile, 0); String
			 * desFileName = sourceFile.getName(); targetFile = new
			 * File(destFile.getPath() + "/" + desFileName); }
			 */

			oFIS = new FileInputStream(sourceFile);
			oFOS = new FileOutputStream(targetFile, false);

			byte[] oBuffer = new byte[1024 * 4];
			int nRead = 0;

			while ((nRead = oFIS.read(oBuffer)) != -1)
			{
				oFOS.write(oBuffer, 0, nRead);
			}

			oFIS.close();
			oFOS.close();
			oFIS = null;
			oFOS = null;

			return true;
		}
		catch (Exception e)
		{
			// TODO 예외처리 필요
			if (oFIS != null)
				oFIS.close();

			if (oFOS != null)
				oFOS.close();

			e.printStackTrace();
			return false;
		}
	}

	public static boolean copyFileByFileChanner(File sourceFile, File destFile) throws IOException
	{
		// FileChanner을 이용하는게 성능이 좀 안좋게 나온다. 파일 크기가 클수록 심하다 ㅡ.ㅡ
		FileChannel source = null;
		FileChannel destination = null;

		try
		{
			if (!checkDirectory(destFile, 1)) return false;

			if (!destFile.exists())
				destFile.createNewFile();

			source = new FileInputStream(sourceFile).getChannel();
			destination = new FileOutputStream(destFile).getChannel();
			destination.transferFrom(source, 0, source.size());

			return true;
		}
		catch (Exception e)
		{
			return false;
		}
		finally
		{
			if (source != null)
				source.close();

			if (destination != null)
				destination.close();
		}
	}

	/**
	 * 지정한 파일(경로)가 존재하는지 검사하여, 경로가 없는 경우 가장 마지막 경로까지 생성합니다.
	 *
	 * @param uri
	 *            파일/경로
	 * @param lvl
	 *            0 : uri 전체를 생성합니다. 1: uri의 부모경로까지만 검사하여 생성합니다.
	 * @return
	 */
	public static boolean checkDirectory(String uri, int lvl)
	{
		return checkDirectory(new File(uri), lvl);
	}

	/**
	 * 지정한 파일(경로)가 존재하는지 검사하여, 경로가 없는 경우 가장 마지막 경로까지 생성합니다.
	 *
	 * @param f
	 *            파일/경로
	 * @param lvl
	 *            0 : f전체를 생성합니다. 1:f의 부모경로까지만 검사하여 생성합니다.
	 * @return
	 */
	public static boolean checkDirectory(File f, int lvl)
	{
		// 좀 많이 귀찮다...ㅡㅡ;;;;; 대충하자..대충...
		try
		{
			if (f.isDirectory())
			{
				if (!f.exists())
				{
					if (f.getParentFile().exists())
					{
						f.mkdir();
					}
					else
					{
						if (checkDirectory(f.getParentFile(), 0))
						{
							f.mkdir();
							return true;
						}
						return false;
					}
				}
				return true;
			}
			if (checkDirectory(f.getParentFile(), 0))
			{
				if (lvl == 0)
				{
					f.mkdir();
				}
				return true;
			}
			return false;

		}
		catch (Exception e)
		{
			return false;
		}
	}

	/**
	 * 지정한 경로에서 중복되지 않는 파일명을 반환합니다.
	 *
	 * @param dir
	 *            검사할 경로
	 * @param fileName
	 *            검사할 파일명 (경로포함되지 않은)
	 * @param includeDirWhenReturn
	 *            반환 할때 경로를 포함할지 여부 (true : 포함, false : 파일명만 반환)
	 * @return
	 */
	public static String getUnduplicateFile(String dir, String fileName, boolean includeDirWhenReturn)
	{
		checkDirectory(dir, 0);

		StringBuffer sb = new StringBuffer();
		String fileExt = getFileExtention(fileName);
		String fileNameBody = getFileNameWithoutExtention(fileName);
		String newFileNameBody = fileNameBody + "_0_";

		sb.append(dir).append("/").append(newFileNameBody).append(".").append(fileExt);

		File checker = new File(sb.toString());
		sb.setLength(0);
		int k = 0;
		while (checker.exists())
		{
			k++;
			sb.setLength(0);
			sb.append(dir).append("/").append(fileNameBody).append("_").append(String.valueOf(k)).append("_").append(".").append(fileExt);
			checker = null;
			checker = new File(sb.toString());
		}

		sb.setLength(0);

		if (includeDirWhenReturn)
			sb.append(dir).append("/").append(fileNameBody).append("_").append(String.valueOf(k)).append("_").append(".").append(fileExt);
		else
			sb.append(fileNameBody).append("_").append(String.valueOf(k)).append("_").append(".").append(fileExt);

		return sb.toString();
	}

	/**
	 * 지정된 경로에서 파일의 확장자를 반환합니다.<br>
	 * "."는 제거된 값을 반환합니다.
	 *
	 * @param uri
	 * @return
	 */
	public static String getFileExtention(String uri)
	{
		if (StringUtil.isNullOrEmpty(uri)) return StringUtil.EMPTY;

		int lastIdx = uri.lastIndexOf('.');
		if (lastIdx > -1) return uri.substring(lastIdx + 1);
		return uri;
	}

	/**
	 * 경로를 제거한 파일명을 반환합니다.
	 *
	 * @param uri
	 * @return
	 */
	public static String getFileName(String uri)
	{
		if (StringUtil.isNullOrEmpty(uri)) return StringUtil.EMPTY;

		String fileName = StringUtil.EMPTY;
		int startPosition = 0;

		if ((startPosition = uri.lastIndexOf(47)) > 0)
		{
			// '/'를 기준
			fileName = uri.substring(startPosition + 1);
		}
		else if ((startPosition = uri.lastIndexOf(92)) > 0)
		{
			// '\'를 기준
			fileName = uri.substring(startPosition + 1);
		}
		else
			fileName = uri;

		return fileName;
	}

	/**
	 * 경로와 확장자를 제거한 파일이름을 반환합니다.
	 *
	 * @param uri
	 * @return
	 */
	public static String getFileNameWithoutExtention(String uri)
	{
		if (StringUtil.isNullOrEmpty(uri)) return StringUtil.EMPTY;

		String fileName = getFileName(uri);
		String ext = getFileExtention(fileName);
		fileName = fileName.replace("." + ext, StringUtil.EMPTY);
		return fileName;
	}

	/**
	 * 파일의 크기를 KB/MB 표시 형태로 가공합니다.
	 *
	 * @param size
	 * @return
	 */
	public static String convertFileSize(long size)
	{
		int divisor = 1;
		String unit = "bytes";
		if (size >= 1024 * 1024 * 1024)
		{
			divisor = 1024 * 1024 * 1024;
			unit = "GB";
		}
		else if (size >= 1024 * 1024)
		{
			divisor = 1024 * 1024;
			unit = "MB";
		}
		else if (size >= 1024)
		{
			divisor = 1024;
			unit = "KB";
		}
		if (divisor == 1) return size / divisor + " " + unit;
		String aftercomma = StringUtil.EMPTY + 100 * (size % divisor) / divisor;

		if (aftercomma.length() == 1) aftercomma = "0" + aftercomma;

		return size / divisor + "." + aftercomma + " " + unit;
	}

}
